import { DEFAULTLAYER } from './constant/index'
import { throttle } from './utils'
import Recorder from './core/recorder'
import BasicLine from './core/shapes/basicLine'
import StraightLine from './core/shapes/straightLine'
import Rectangle from './core/shapes/rectangle'
import Circle from './core/shapes/circle'
import Image from './core/shapes/image'
import CreateImage from './core/shapes/createShape/createImage'

let animationId = null

class Drawing {
  constructor(props) {
    this.canvas = null
    this.ctx = null
    this.lineColor = props.lineColor || '#000'
    this.lineWidth = props.lineWidth || 1
    this.drawType = props.drawType || 'basicline'
    this.backgroundColor = props.backgroundColor || '#eee'
    this.shapes = {}
    this.steps = [] // 撤销记录
    this.container = this._initContainer(props.el)
    this.recorder = null
    this._initCanvas()
  }

  /**
   * 设置线条颜色
   * @param color rgb
   */
  setLineColor(color) {
    this.lineColor = color
  }

  /**
   * 设置线条宽度
   * @param width
   */
  setLineWidth(width) {
    this.lineWidth = width
  }

  /**
   * 设置绘制类型
   * @param drawType
   */
  setDrawType(drawType) {
    this.drawType = drawType
  }

  /**
   * 撤销
   */
  goback() {
    const lastStep = this.steps.pop()
    if (lastStep || lastStep === 0) {
      this.shapes[lastStep].pop()
      this._draw()
    }
  }

  /**
   * 创建图片
   * @param props
   */
  createImage({ src, width, height, left, top, layer }) {
    let imageBox = new CreateImage({ container: this.container })
    imageBox.createImage({ src, width, height, left, top }, (imgInfo) => {
      const image = new Image({
        image: imgInfo.image,
        startX: imgInfo.left,
        startY: imgInfo.top,
        width: imgInfo.width,
        height: imgInfo.height
      })
      image.setLayer(layer ? layer + DEFAULTLAYER : image.getLayer())
      this.shapes[image.getLayer()] = this.shapes[image.getLayer()] || []
      this.shapes[image.getLayer()].push(image)
      this.steps.push(image.getLayer())

      this._draw()
      imageBox = null
    })
  }

  /**
   * 开始录制
   */
  startRecorder() {
    this.recorder = new Recorder({
      canvas: this.canvas,
      frameRate: 15
    })
    this.recorder.start()
  }

  stopRecorder() {
    if (!this.recorder) {
      throw new Error('请先调用 startRecorder 方法')
    }
    return this.recorder.stop()
  }

  /**
   * 创建容器
   * @param dom
   * @returns HTMLDivElement
   */
  _initContainer(dom) {
    const container = document.createElement('div')
    container.style.position = 'relative'
    container.style.width = '100%'
    container.style.height = '100%'
    dom.appendChild(container)
    return container
  }

  /**
   * 初始化画布
   */
  _initCanvas() {
    const cWidth = this.container.clientWidth
    const cHeight = this.container.clientHeight
    const canvas = document.createElement('canvas')
    canvas.width = cWidth
    canvas.height = cHeight
    canvas.style.width = '100%'
    canvas.style.height = '100%'
    canvas.style.cursor = 'crosshair'
    canvas.style.background = this.backgroundColor
    this.container.appendChild(canvas)

    this.canvas = canvas
    this.ctx = canvas.getContext('2d')
    this._registerBaseShapeEvent()
  }

  /**
   * 注册事件
   */
  _registerBaseShapeEvent() {
    const rect = this.canvas.getBoundingClientRect()
    let insideShape = null

    const canvasMouseMove = (e) => {
      if (Object.keys(this.shapes).length === 0) return
      const moveX = e.clientX - rect.left
      const moveY = e.clientY - rect.top
      insideShape = this._getInsideShape(moveX, moveY, this.drawType)
      if (insideShape) {
        this.canvas.style.cursor = 'pointer'
      } else {
        this.canvas.style.cursor = 'crosshair'
      }
    }
    const throttledMouseMove = throttle(canvasMouseMove, 300)

    this.canvas.onmousedown = (e) => {
      this.canvas.onmousemove = null
      this._drawFrame()
      let startX = e.clientX - rect.left
      let startY = e.clientY - rect.top
      let shape
      if (!insideShape) {
        shape = this._getBaseShapeType(startX, startY)
        const layer = shape.getLayer()
        this.shapes[layer] = this.shapes[layer] || []
        this.shapes[layer].push(shape)
        this.steps.push(layer)
      } else {
        shape = insideShape
      }

      window.onmousemove = (e) => {
        let endX = e.clientX - rect.left
        let endY = e.clientY - rect.top
        if (endX <= 0) endX = 0 + this.lineWidth
        if (endX >= rect.width) endX = rect.width - this.lineWidth
        if (endY <= 0) endY = 0 + this.lineWidth
        if (endY >= rect.height) endY = rect.height - this.lineWidth
        if (insideShape) {
          let dx = endX - startX
          let dy = endY - startY
          if (shape.maxX && shape.maxX + dx >= rect.width) dx = 0
          if (shape.minX && shape.minX + dx <= 0) dx = 0
          if (shape.maxY && shape.maxY + dy >= rect.height) dy = 0
          if (shape.minY && shape.minY + dy <= 0) dy = 0
          shape?.changePosition?.(dx, dy)
          startX = endX
          startY = endY
        } else {
          shape?.setEnd?.(endX, endY)
        }
      }
      window.onmouseup = () => {
        cancelAnimationFrame(animationId)
        window.onmousemove = null
        window.onmouseup = null
        this.canvas.onmousemove = throttledMouseMove
      }
    }

    this.canvas.ondblclick = (e) => {
      const startX = e.clientX - rect.left
      const startY = e.clientY - rect.top
      for (const key in this.shapes) {
        const shapes = this.shapes[key] || []
        for (let index = shapes.length - 1; index >= 0; index--) {
          const shape = shapes[index]
          if (shape instanceof Image) {
            if (shape.inside(startX, startY)) {
              this.createImage({
                src: shape.src,
                width: shape.width,
                height: shape.height,
                left: shape.startX,
                top: shape.startY
              })
              this.shapes[key].splice(index, 1)
              this.draw()
              break
            }
          }
        }
      }
    }
  }

  _getInsideShape(x, y, type) {
    for (const key in this.shapes) {
      const shapes = this.shapes[key] || []
      for (let i = shapes.length - 1; i >= 0; i--) {
        const shape = shapes[i]
        if (shape.getType() === type && shape?.inside?.(x, y)) {
          return shape
        }
      }
    }

    return null
  }

  _getBaseShapeType(startX, startY) {
    const props = { startX, startY }
    let shape = {}
    switch (this.drawType) {
      case 'basicline':
        shape = new BasicLine(props)
        break
      case 'straightline':
        shape = new StraightLine(props)
        break
      case 'rectangle':
        shape = new Rectangle(props)
        break
      case 'circle':
        shape = new Circle(props)
        break
      default:
        shape = new BasicLine(props)
        break
    }
    return shape
  }

  _draw() {
    this.ctx.fillStyle = this.backgroundColor
    this.ctx.fillRect(0, 0, this.canvas?.width || 0, this.canvas?.height || 0)

    this.ctx.lineWidth = this.lineWidth
    this.ctx.strokeStyle = this.lineColor
    for (const key in this.shapes) {
      const shapes = this.shapes[key]
      if (shapes && shapes.length) {
        for (const shape of shapes) {
          shape?.draw?.(this.ctx)
        }
      }
    }
  }

  _drawFrame() {
    animationId = requestAnimationFrame(this._drawFrame.bind(this))
    this._draw()
  }
}

export default Drawing
